/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2022. All rights reserved.
 * Description:
 * Author: g00421808
 * Create: 6/24/2022
 * Notes:
 */

#ifndef JSBIND_NAPI_FUNCTION_BINDER_H
#define JSBIND_NAPI_FUNCTION_BINDER_H

#include <array>
#include <tuple>
#include <node_api.h>
#include "jsbind/logging/logging.h"

#include "jsbind/value/napi/napi_value_trait.h"
#include "jsbind/binder/function_binder.h"
#include "jsbind/checker/napi/napi_checker.h"

#if JSBIND_USING_TRACING
# include <tracing/trace.h>
#else
# define FUNCTION_DTRACE()
#endif // JSBIND_USING_TRACING

namespace Jsb {

template <typename F, typename R, typename... P>
class NapiFunctionBinder : public FunctionBinder<NapiFunctionBinder<F, R, P...>, F, R, P...> {
public:
    typedef FunctionBinder<NapiFunctionBinder<F, R, P...>, F, R, P...> ParentType;

    static napi_value Wrapper (napi_env env, napi_callback_info info) {
        FUNCTION_DTRACE();
        JSB_DLOG(DEBUG) << "NapiFunctionBinder::Wrapper";
        if (!Checker<P...>::IsArityValid(env, info)) {
            std::string msg = std::string("JSBind: Wrong number of arguments, expected: ").append(std::to_string(sizeof...(P)));
            napi_throw_error(env, nullptr, msg.c_str());
            return nullptr;
        }
        if (!Checker<typename ValueDefiner<P>::RawType...>::TypesAreValid(env, info)) {
            std::string msg = Checker<typename ValueDefiner<P>::RawType...>::GetTypeError(env, info);
            napi_throw_type_error(env, "JSBind: Wrong type of arguments", msg.c_str());
            return nullptr;
        }
        return SafetyWrapper(env, info, std::make_index_sequence<sizeof...(P)>());
    }

private:
    template<size_t... Index>
    static napi_value SafetyWrapper(napi_env& env, napi_callback_info info, std::index_sequence<Index...>) {
        FUNCTION_DTRACE();
        napi_status status;
        Jsb::BindInfo* bindInfo = nullptr;
        size_t argc = sizeof...(Index);
        std::array<napi_value, sizeof...(Index)> args;

        status = napi_get_cb_info(env, info, &argc, args.data(), nullptr, ((void**)&bindInfo));
        JSB_DCHECK(status == napi_ok);
        JSB_DCHECK(bindInfo != nullptr);
        auto tuple = std::make_tuple(NapiValueMaker<typename ValueDefiner<P>::RawType>(env, args[Index])...);
        auto valueTuple = std::make_tuple(NapiValueTrait<typename ValueDefiner<P>::RawType>::Cast(std::get<Index>(tuple))...);

        return WrapperForward(env, bindInfo, std::get<Index>(valueTuple)...);
    }

    template<typename... Args>
    static napi_value WrapperForward(napi_env& env, Jsb::BindInfo* bindInfo, Args&&... args) {
        FUNCTION_DTRACE();
        if constexpr (std::is_void<R>::value) {
            ParentType::InnerWrapper(bindInfo, std::forward<Args>(args)...);
            return nullptr;
        } else {
            return NapiValue<R>::ToNapiValue(env, ParentType::InnerWrapper(bindInfo, std::forward<Args>(args)...));
        }
    }
};
} // namespace Jsb
#endif //JSBIND_NAPI_FUNCTION_BINDER_H
